/* Copyright 2009
 *
 * This program and the accompanying materials
 * are made available under the terms of the
 * Eclipse Public License v1.0 which accompanies
 * this distribution, and is available at
 *
 * 		http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
 * OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * Contributors:
 * 	   IBM Corporation - initial API and implementation for JDT/DLTK
 *     Sean W. Quinn - initial adoption for use with PHP from various sources.
 */
package org.eclipse.php.internal.formatter;

import org.eclipse.php.internal.core.ast.scanner.AstLexer;
import org.eclipse.php.internal.formatter.align.Alignment;

/**
 * This class is responsible for dumping formatted source
 *
 * @since 2.1
 */
public class Scribe {
	/** one-based column */
	public int column;

	public Alignment currentAlignment;

	// edits management
	private OptimizedReplaceEdit[] edits;
	public int editsIndex;

	/** The level of indentation, are we 1, 2, 3 indentions in? */
	public int indentationLevel;
	public int lastNumberOfNewLines;
	public int line;

	public Alignment memberAlignment;
	public boolean needSpace = false;

	public int nlsTagCounter;
	public int pageWidth;
	public boolean pendingSpace = false;

	private String lineSeparator;

	public AstLexer scanner;

	/** The size of the indentation, how many spaces or tabs to indent by. */
	public int indentationSize;

	/** The tab character. */
	public int tabChar;

	/** Whether to use tabs only for leading indentions, or for all. */
	private boolean useTabsOnlyForLeadingIndents;

	private final void addDeleteEdit(int start, int end) {
		if (this.edits.length == this.editsIndex) {
			// resize
			resize();
		}
		//addOptimizedReplaceEdit(start, end - start + 1, Util.EMPTY_STRING);
		addOptimizedReplaceEdit(start, end - start + 1, new String(new char[0]));
	}

	public final void addInsertEdit(int insertPosition, String insertedString) {
		if (this.edits.length == this.editsIndex) {
			// resize
			resize();
		}
		addOptimizedReplaceEdit(insertPosition, 0, insertedString);
	}

	private final void addOptimizedReplaceEdit(int offset, int length, String replacement) {
		if (this.editsIndex > 0) {
			// try to merge last two edits
			final OptimizedReplaceEdit previous = this.edits[this.editsIndex - 1];
			final int previousOffset = previous.offset;
			final int previousLength = previous.length;
			final int endOffsetOfPreviousEdit = previousOffset + previousLength;
			final int replacementLength = replacement.length();
			final String previousReplacement = previous.replacement;
			final int previousReplacementLength = previousReplacement.length();
			if (previousOffset == offset && previousLength == length
					&& (replacementLength == 0 || previousReplacementLength == 0)) {
				if (this.currentAlignment != null) {
					final Location location = this.currentAlignment.location;
					if (location.editsIndex == this.editsIndex) {
						location.editsIndex--;
						location.textEdit = previous;
					}
				}
				this.editsIndex--;
				return;
			}
			if (endOffsetOfPreviousEdit == offset) {
				if (length != 0) {
					if (replacementLength != 0) {
						this.edits[this.editsIndex - 1] = new OptimizedReplaceEdit(previousOffset, previousLength
								+ length, previousReplacement + replacement);
					}
					else if (previousLength + length == previousReplacementLength) {
						// check the characters. If they are identical, we can get rid of the previous edit
						boolean canBeRemoved = true;
						loop: for (int i = previousOffset; i < previousOffset + previousReplacementLength; i++) {
//							if (this.scanner.source[i] != previousReplacement.charAt(i - previousOffset)) {
//								this.edits[this.editsIndex - 1] = new OptimizedReplaceEdit(previousOffset,
//										previousReplacementLength, previousReplacement);
//								canBeRemoved = false;
//								break loop;
//							}
						}
						if (canBeRemoved) {
							if (this.currentAlignment != null) {
								final Location location = this.currentAlignment.location;
								if (location.editsIndex == this.editsIndex) {
									location.editsIndex--;
									location.textEdit = previous;
								}
							}
							this.editsIndex--;
						}
					}
					else {
						this.edits[this.editsIndex - 1] = new OptimizedReplaceEdit(previousOffset, previousLength
								+ length, previousReplacement);
					}
				}
				else {
					if (replacementLength != 0) {
						this.edits[this.editsIndex - 1] = new OptimizedReplaceEdit(previousOffset, previousLength,
								previousReplacement + replacement);
					}
				}
			}
			else if ((offset + length == previousOffset)
					&& (previousLength + length == replacementLength + previousReplacementLength)) {
				// check if both edits corresponds to the orignal source code
				boolean canBeRemoved = true;
				String totalReplacement = replacement + previousReplacement;
				loop: for (int i = 0; i < previousLength + length; i++) {
//					if (this.scanner.source[i + offset] != totalReplacement.charAt(i)) {
//						this.edits[this.editsIndex - 1] = new OptimizedReplaceEdit(offset, previousLength + length,
//								totalReplacement);
//						canBeRemoved = false;
//						break loop;
//					}
				}
				if (canBeRemoved) {
					if (this.currentAlignment != null) {
						final Location location = this.currentAlignment.location;
						if (location.editsIndex == this.editsIndex) {
							location.editsIndex--;
							location.textEdit = previous;
						}
					}
					this.editsIndex--;
				}
			}
			else {
				this.edits[this.editsIndex++] = new OptimizedReplaceEdit(offset, length, replacement);
			}
		}
		else {
			this.edits[this.editsIndex++] = new OptimizedReplaceEdit(offset, length, replacement);
		}
	}

	/**
	 * <p>
	 * The next level of indentation is based on a column's estimated position
	 * (if column is not indented, then use {@link Scribe#indentationLevel}).
	 * </p>
	 *
	 * @param someColumn the column we are basing our next indentation level off
	 *            of.
	 * @return the next level of indentation.
	 */
	public int getNextIndentationLevel(int someColumn) {
		int indent = someColumn - 1;
		if (indent == 0)
			return this.indentationLevel;
		if (this.tabChar == PHPCodeFormatterOptions.TAB) {
			if (this.useTabsOnlyForLeadingIndents) {
				return indent;
			}
			int rem = indent % this.indentationSize;
			int addition = rem == 0 ? 0 : this.indentationSize - rem; // round to superior
			return indent + addition;
		}
		return indent;
	}

	/**
	 *
	 */
	public void printNewLine() {
		this.printNewLine(this.scanner.yylength() + 1);
	}

	/**
	 * @param insertPosition
	 */
	public void printNewLine(int insertPosition) {
		if (this.nlsTagCounter > 0) {
			return;
		}
		if (this.lastNumberOfNewLines >= 1) {
			this.column = 1; // ensure that the scribe is at the beginning of a new line
			return;
		}
		addInsertEdit(insertPosition, this.lineSeparator);
		this.line++;
		this.lastNumberOfNewLines = 1;
		this.column = 1;
		this.needSpace = false;
		this.pendingSpace = false;
	}

	private void resize() {
		System.arraycopy(this.edits, 0, (this.edits = new OptimizedReplaceEdit[this.editsIndex * 2]), 0, this.editsIndex);
	}
}
